package com.ruoyi.model.apect;

import com.ruoyi.common.annotation.RequestLock;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.model.auth.AuthContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author : 张怀秋
 * 防止用户重复提交切面类
 **/
@Aspect
@Component
@Order(2)
public class RedisRequestLockAspect {

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 由于后台是内部人员自行管理 哪怕后续除了管理员账号还有其他账号 内部人员的操作基本也不会影响到大局效果
     * 所以redis的key 这次直接用前台用户id + 方法特定前缀就可以了
     *
     * @param joinPoint 连接点
     * @return O
     * @throws Throwable 异常
     */
    @Around("@annotation(com.ruoyi.common.annotation.RequestLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
        //1. 通过连接点获取到注解
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        Method method = methodSignature.getMethod();

        RequestLock annotation = method.getAnnotation(RequestLock.class);

        //2. 获取到注解中的内容
        String prefix = annotation.prefix(); //锁前缀
        if (StringUtils.isEmpty(prefix)) {
            throw new RuntimeException("重复提交前缀不能为空!"); //结束一切
        }
        //最终锁前缀
        final String lockKey = prefix + ":" + AuthContextHolder.getUserId();
        int expire = annotation.expire(); //锁过期时间
        TimeUnit timeUnit = annotation.timeUnit(); //单位

        // 使用RedisCallback接口执行set命令，设置锁键；设置过期时间和SET_IF_ABSENT选项
        Boolean execute = stringRedisTemplate.execute(
                (RedisCallback<Boolean>) connection -> connection.set(
                        lockKey.getBytes(), //键名
                        new byte[0], //键值 设置一个占位值
                        Expiration.from(expire, timeUnit),
                        RedisStringCommands.SetOption.SET_IF_ABSENT)
        );

        if (Boolean.FALSE.equals(execute)) {
            throw new RuntimeException("您的操作太快了 请稍等一下吧~");
        }

        return joinPoint.proceed();
    }

}
